﻿/******************************************************************************
    myBoard is a port of MacOS X dashboard application for windows.
    Copyright (C) 2007,2008  by Yann ALET
    Email : yann.alet@gmail.com

    myBoard is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    myBoard is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
****************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using myBoard.Framework;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Globalization;
using myBoard.Framework.Forms;
using System.Windows;
using myBoard.App.Model;
using ICSharpCode.SharpZipLib.Zip;
using System.Diagnostics;

namespace myBoard.App.Business
{
    public static class MagnetsManager
    {
        public delegate void NewMagnetDetectedHandler(List<MagnetEx> magnets);
        public static event NewMagnetDetectedHandler NewMagnetDetected;
        static List<MagnetEx> _latestDetectedMagnets;
        static List<string> _latestDetectedMagnetFiles = new List<string>();
        static Dictionary<string,List<Assembly>> _referencedAssemblies = new Dictionary<string,List<Assembly>>();
        static bool _isFirstAttemptToDetectInstalledMagnets = true;
        static public bool MustRestart { get; set; }

        // Methods
        public static MagnetEx CreateMagnetInstance(string magnetPath)
        {
            try
            {
                if (!File.Exists(magnetPath))
                {
                    return null;
                }
                Assembly assembly = Assembly.LoadFile(magnetPath);
                Type[] types = assembly.GetTypes();
                foreach (Type type in types)
                {
                    if (type.GetInterface("IMagnet") != null)
                    {
                        IMagnet magnet = assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null, CultureInfo.InstalledUICulture, null) as IMagnet;
                        if (magnet != null)
                        {
                            MagnetEx mEx = new MagnetEx(magnet) 
                            {
                                FullyQualifiedName = magnetPath,
                                Version = assembly.GetName().Version
                            };
                            return mEx;
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                ExceptionsManager.LogException("myBoard", exception, true);
            }
            return null;
        }

        static AppDomain _currentDomain;

        public static List<MagnetEx> GetInstalledMagnets()
        {
            try
            {
                if (_isFirstAttemptToDetectInstalledMagnets &&
                    InstancesSingleton.Instances.CurrentConfiguration.MagnetsToDeleteOnNextStart != null)
                {
                    foreach (string magnet in InstancesSingleton.Instances.CurrentConfiguration.MagnetsToDeleteOnNextStart)
                    {
                        if (File.Exists(magnet))
                        {
                            string parentDir = Path.GetDirectoryName(magnet);
                            Directory.Delete(parentDir, true);
                        }
                    }
                    InstancesSingleton.Instances.CurrentConfiguration.MagnetsToDeleteOnNextStart.Clear();
                    ConfigurationSerializer confSerializer = new ConfigurationSerializer();
                    confSerializer.Serialize(InstancesSingleton.Instances.CurrentConfiguration);
                }
                
                RefreshReferencedAssemblies();
                if (MustRestart) //This happens if new references have been founds, we must then restart myBoard
                {
                    return new List<MagnetEx>();
                }

                if (_currentDomain == null)
                {
                    _currentDomain = AppDomain.CurrentDomain;
                    _currentDomain.AssemblyResolve += new ResolveEventHandler(_currentDomain_AssemblyResolve);
                }

                string magnetsPath = Path.Combine(Path.Combine(Environment.GetEnvironmentVariable("AppData"), "myBoard"), "Magnets");
                if (!Directory.Exists(magnetsPath))
                {
                    _isFirstAttemptToDetectInstalledMagnets = false;
                    Directory.CreateDirectory(magnetsPath);
                    return new List<MagnetEx>();
                }
                DirectoryInfo dis = new DirectoryInfo(magnetsPath);
                List<MagnetEx> list = new List<MagnetEx>();
                foreach (DirectoryInfo di in dis.GetDirectories())
                {
                    FileInfo[] files = di.GetFiles("*.dll");                    
                    for (int i = 0; i < files.Length; i++)
                    {
                        FileInfo info2 = files[i];
                        if (_latestDetectedMagnetFiles.Contains(info2.FullName))
                            continue;

                        _latestDetectedMagnetFiles.Add(info2.FullName);
                        try
                        {
                            Assembly assembly = Assembly.LoadFile(info2.FullName);

                            Type[] types = assembly.GetTypes();
                            foreach (Type type in types)
                            {
                                if (type.GetInterface("IMagnet") != null)
                                {
                                    IMagnet magnet = assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null, CultureInfo.InstalledUICulture, null) as IMagnet;
                                    if (magnet != null)
                                    {
                                        MagnetEx item = new MagnetEx(magnet)
                                        {
                                            FullyQualifiedName = info2.FullName,
                                            Version = assembly.GetName().Version
                                        };
                                        list.Add(item);
                                    }
                                }
                            }
                            List<MagnetEx> newMagnets = new List<MagnetEx>();
                            if (!_isFirstAttemptToDetectInstalledMagnets)
                            {
                                foreach (MagnetEx detectedMagnet in list)
                                {
                                    bool alreadyDetected = false;
                                    if (_latestDetectedMagnets != null)
                                    {
                                        foreach (MagnetEx magnet in _latestDetectedMagnets)
                                        {
                                            if (detectedMagnet.StoreID == magnet.StoreID)
                                            {
                                                if (detectedMagnet.Version.Major == magnet.Version.Major
                                                    && detectedMagnet.Version.Minor == magnet.Version.Minor)
                                                    alreadyDetected = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (!alreadyDetected)
                                    {
                                        newMagnets.Add(detectedMagnet);
                                    }
                                }
                            }
                            _latestDetectedMagnets = list;
                            if (NewMagnetDetected != null)
                                NewMagnetDetected(newMagnets);
                        }
                        catch (Exception ex)
                        {
                            MessageBoxResult result = myBoard.Framework.Forms.MessageBox.Show(string.Format("Could not load magnet {0}", info2.Name),
                                string.Format("myBoard failed to load {0}, maybe this magnet is not compatible with myBoard anymore.\r\nDo you want myBoard to delete this magnet so it won't try to load it next time ?\r\n\r\nError details :{1}", info2.Name, ex.ToString()),
                                "myBoard", System.Diagnostics.EventLogEntryType.Warning, MessageBoxButton.YesNo);
                            if (result == MessageBoxResult.Yes)
                            {
                                myBoard.App.Model.InstancesSingleton.Instances.CurrentConfiguration.MagnetsToDeleteOnNextStart.Add(info2.FullName);
                                ConfigurationSerializer s = new ConfigurationSerializer();
                                s.Serialize(myBoard.App.Model.InstancesSingleton.Instances.CurrentConfiguration);
                            }
                            _isFirstAttemptToDetectInstalledMagnets = false;
                        }
                    }
                }
                _isFirstAttemptToDetectInstalledMagnets = false;
                return list;
            }
            catch (Exception exception)
            {
                ExceptionsManager.LogException("myBoard", exception, true);
                _isFirstAttemptToDetectInstalledMagnets = false;
            }
            return null;
        }

        static Assembly _currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            StackTrace stackTrace = new StackTrace();
            StackFrame[] frames = stackTrace.GetFrames();
            string callingAssemblyPath = string.Empty;
            foreach (StackFrame frame in frames)
            {
                MethodBase method = frame.GetMethod();
                if (_referencedAssemblies.ContainsKey(method.DeclaringType.Assembly.Location))
                {
                    callingAssemblyPath = method.DeclaringType.Assembly.Location;
                    break;
                }
                //Debug.WriteLine(method.DeclaringType.Assembly.FullName);
            }

            if (!string.IsNullOrEmpty(callingAssemblyPath))
            {
                foreach (Assembly assembly in _referencedAssemblies[callingAssemblyPath])
                {
                    string currentAssemblyName = assembly.FullName.Substring(0, assembly.FullName.IndexOf(","));
                    string targetAssemblyName = args.Name.Substring(0, assembly.FullName.IndexOf(","));
                    if (currentAssemblyName == targetAssemblyName)
                        return assembly;
                }
            }
            else
            {
                foreach (string key in _referencedAssemblies.Keys)
                {
                    foreach (Assembly ass in _referencedAssemblies[key])
                    {
                        if (ass.FullName == args.Name)
                            return ass;
                    }
                }
            }
            ExceptionsManager.LogException("myBoard", new Exception(string.Format("myBoard could not resolve requested assembly {0}.", args.Name)), false);
            return null;
        }

        static void RefreshReferencedAssemblies()
        {
            _referencedAssemblies = new Dictionary<string, List<Assembly>>();


            string magnetsPath = Path.Combine(Path.Combine(Environment.GetEnvironmentVariable("AppData"), "myBoard"), "Magnets");
            if (Directory.Exists(magnetsPath))
            {
                DirectoryInfo magnetsDirectoryInfo = new DirectoryInfo(magnetsPath);
                DirectoryInfo[] dis = magnetsDirectoryInfo.GetDirectories();
                foreach (DirectoryInfo mDirectory in dis)
                {
                    FileInfo[] potentialMagnet = mDirectory.GetFiles("*.dll");
                    if (potentialMagnet != null && potentialMagnet.Length > 0)
                    {
                        string refAssembliesDir = Path.Combine(mDirectory.FullName, "Referenced Assemblies");
                        if (Directory.Exists(refAssembliesDir))
                        {
                            FileInfo[] assemblies = new DirectoryInfo(refAssembliesDir).GetFiles("*.dll");
                            List<Assembly> ass = new List<Assembly>();
                            foreach (FileInfo fi in assemblies)
                            {
                                try
                                {
                                    Assembly assembly = Assembly.LoadFile(fi.FullName);
                                    ass.Add(assembly);
                                    if (!File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileName(fi.FullName))))
                                    {
                                        File.Copy(fi.FullName, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileName(fi.FullName)), false);
                                        MustRestart = true;
                                    }

                                }
                                catch (Exception ex)
                                {
                                    ExceptionsManager.LogException("myBoard", ex, false);
                                }
                            }
                            _referencedAssemblies.Add(potentialMagnet[0].FullName, ass);
                        }
                    }

                }                             
            }            
        }

        public static List<MagnetEx> LoadMagnets()
        {
            try
            {
                string path = Path.Combine(Path.Combine(Environment.GetEnvironmentVariable("AppData"), "myBoard"), "magnets.xml");
                string str3 = Path.Combine(Path.Combine(Environment.GetEnvironmentVariable("AppData"), "myBoard"), "Magnets");
                if (!File.Exists(path))
                {
                    return null;
                }
                List<MagnetEx> list = new MagnetsSerializer().DeSerialize(path);
                for (int i = 0; i < list.Count; i++)
                {
                    try
                    {
                        MagnetEx deserializedMagnet = list[i];
                        if (Directory.Exists(str3))
                        {
                            Assembly assembly = Assembly.LoadFile(deserializedMagnet.FullyQualifiedName);
                            Type[] types = assembly.GetTypes();
                            foreach (Type type in types)
                            {
                                if (type.GetInterface("IMagnet") != null)
                                {
                                    IMagnet magnet = assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null, CultureInfo.InstalledUICulture, null) as IMagnet;
                                    if (magnet != null)
                                    {
                                        MagnetEx ex2 = new MagnetEx(magnet, deserializedMagnet)
                                        {
                                            FullyQualifiedName = deserializedMagnet.FullyQualifiedName,
                                            Version = assembly.GetName().Version
                                        };
                                        list[i] = ex2;
                                        break;
                                    };
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        ExceptionsManager.LogException("myBoard", ex, true);
                        list.RemoveAt(i);
                        i--;
                    }
                }
                return list;
            }
            catch (Exception exception)
            {
                ExceptionsManager.LogException("myBoard", exception, true);
            }
            return null;
        }

        public static void SaveMagnets(List<MagnetEx> magnets)
        {
            try
            {
                string path = Path.Combine(Environment.GetEnvironmentVariable("AppData"), "myBoard");
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                if (magnets != null)
                {
                    new MagnetsSerializer().Serialize(magnets, Path.Combine(path, "magnets.xml"));
                }
            }
            catch (Exception exception)
            {
                ExceptionsManager.LogException("myBoard", exception, true);
            }
        }


        public static Assembly GetMagnetAssembly(string magnetPath)
        {
            try
            {
                if (!File.Exists(magnetPath))
                {
                    return null;
                }
                string tempExtractionPath = Path.Combine(Path.GetTempPath(),Guid.NewGuid().ToString());
                FastZip fastZip = new FastZip();
                fastZip.ExtractZip(magnetPath, tempExtractionPath, null);
                FileInfo[] files = new DirectoryInfo(tempExtractionPath).GetFiles("*.dll");
                foreach (FileInfo fi in files)
                {
                    try
                    {
                        Assembly assembly = Assembly.LoadFile(fi.FullName);
                        Type[] types = assembly.GetTypes();
                        foreach (Type type in types)
                        {
                            if (type.GetInterface("IMagnet") != null)
                            {
                                return assembly;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        ExceptionsManager.LogException("Magnet Store", ex, false);
                    }
                }
            }
            catch (Exception exception)
            {
                ExceptionsManager.LogException("myBoard", exception, true);
            }
            return null;
        }

    }
}

